#include "kernel.h"
#include "libasm.h"
#include "console.h"
#include "keyboard.h"
#include "stdio.h"
#include "shell.h"
#include "scheduler.h"
#include "command.h"
#include "video.h"
#include "shm.h"

DESCR_INT idt[0x81];
IDTR idtr;
DESCR_SEG gdt[0x05];
GDTR gdtr;


unsigned long *page_directory = (unsigned long *)	0x9B000;
unsigned long *page_table1 = (unsigned long *) 		0x9C000;
unsigned long *page_table2 = (unsigned long *) 		0x9D000;
unsigned long *page_table3 = (unsigned long *) 		0x9E000;

void setup_IDT_entry (DESCR_INT *item, byte selector, dword offset,
		byte access, byte cero);
void SetupIDT(void);
void setup_GDT_entry (DESCR_SEG* item, dword base, dword limit, byte access, byte attribs);
static void SetupGDT(void);
static void SetupPaging(void);



void k_main(void)
{
	SetupIDT();
	SetupGDT();
	SetupPaging();

	/* Apagamos el cursor por hardware */
	_DelCursor();

	/* Inicializo vector de consolas y creo la primera. */
	init_tty();

	/* Inicializamos el multitasking */
	initialize_multitasking();

	/* Inicializo las estructuras del vector de Shared Memories */
	initShm();

	/* Habilito interrupcion de timer tick y del teclado */
	_Cli();
        maskPIC1(0xFC);
        maskPIC2(0xFF);

	/* Iniciar el Sistema Operativo (al final de start_init()
	 * se ejecuta _Sti) */
	start_init();

	while (1); /* defensivo */

	return;
}


void
SetupIDT(void)
{

	/* Timer Tick */
    setup_IDT_entry (&idt[0x08], 0x08, (dword)&int_08_hand, ACS_INT, 0);
	/* Teclado */
	setup_IDT_entry (&idt[0x09], 0x08, (dword)&int_09_hand, ACS_INT, 0);


	/* Excepciones */
	setup_IDT_entry (&idt[0x06], 0x08, (dword)&invop_hand, ACS_INT, 0);
	setup_IDT_entry (&idt[0x0C], 0x08, (dword)&ssf_hand, ACS_INT, 0);
	setup_IDT_entry (&idt[0x0D], 0x08, (dword)&gpf_hand, ACS_INT, 0);
	setup_IDT_entry (&idt[0x0B], 0x08, (dword)&snp_hand, ACS_INT, 0);
	setup_IDT_entry (&idt[0x0E], 0x08, (dword)&pagefault_hand, ACS_INT, 0);
	setup_IDT_entry (&idt[0x05], 0x08, (dword)&bounds_hand, ACS_INT, 0);
	setup_IDT_entry (&idt[0x00], 0x08, (dword)&div0_hand, ACS_INT, 0);
	setup_IDT_entry (&idt[0x80], 0x08, (dword)&int_80_hand, ACS_INT, 0);

	/* Carga de IDTR    */
	idtr.base =(dword) &idt;
	idtr.limit = sizeof(idt)-1;
	_lidt (&idtr);

}

void setup_IDT_entry (DESCR_INT *item, byte selector, dword offset, byte access,
			 byte cero)
{
	item->selector = selector;
	item->offset_l = offset & 0xFFFF;
	item->offset_h = offset >> 16;
	item->access = access;
	item->cero = cero;
}

void setup_GDT_entry (DESCR_SEG* item, dword base, dword limit, byte access, byte attribs)
{
	item->base_l=base & 0xffff;
	item->base_m=(base>>16)&0xff;
	item->base_h=(base>>24);
	item->limit=limit & 0xffff;
	item->attribs= attribs | ((limit>>16) &0x0f);
	item->access=access;

}

static void SetupGDT()
{
	/* Seteo la GDT para modo flat un segmento de codigo y otro de datos
	 * que ocupan toda la memoria*/
	setup_GDT_entry (&gdt[0x01], 0, 0xFFFFF, ACS_CODE, 0xC0);
	setup_GDT_entry (&gdt[0x02], 0, 0xFFFFF, ACS_STACK, 0xC0);
	/* Carga de GDTR */
	gdtr.base =(dword) &gdt;
	gdtr.limit = sizeof(gdt)-1;
	_lgdt(&gdtr);
}

static void SetupPaging()
{
	unsigned long address=0; // holds the physical address of where a page is
	unsigned int i;

	// map the first 4MB of memory
	for(i=0; i<1024; i++)
	{
		page_table1[i] = address | 3; // attribute set to: supervisor level, read/write, present(011 in binary)
		address = address + 4096; // 4096 = 4kb
	};

	page_table2[0] = address | 3; // attribute set to: supervisor level, read/write, present(011 in binary)
	address = address + 4096; // 4096 = 4kb

	for(i=1; i<1024; i++)
	{
		page_table2[i] = address | 2; // attribute set to: supervisor level, read/write, not present(010 in binary)
		address = address + 4096; // 4096 = 4kb
	};


	for(i=0; i<1024; i++)
	{
		page_table3[i] = address | 3; // attribute set to: supervisor level, read/write, present(011 in binary)
		address = address + 4096; // 4096 = 4kb
	};
	// fill the first entry of the page directory
	page_directory[0] = (unsigned long)page_table1; // attribute set to: supervisor level, read/write, present(011 in binary)
	page_directory[0] = page_directory[0] | 3;

	page_directory[1] = (unsigned long)page_table2; // attribute set to: supervisor level, read/write, present(011 in binary)
	page_directory[1] = page_directory[1] | 3;

	page_directory[2] = (unsigned long)page_table3; // attribute set to: supervisor level, read/write, present(011 in binary)
	page_directory[2] = page_directory[2] | 3;

	for(i=3; i<1024; i++)
	{
		page_directory[i] = 0 | 2; // attribute set to: supervisor level, read/write, not present(010 in binary)
	};

	write_cr3((unsigned long)page_directory); // put that page directory address into CR3
	write_cr0(read_cr0() | 0x80000000); // set the paging bit in CR0 to 1

}

void
int_08()
{
	//_Cli();
	show_multitasker();
	scheduler();
	//_Sti();
}


int
int_09(int scancode)
{
	int next_tty;
	//_Cli();
	next_tty = handle_scancode( (unsigned char)scancode );
	//_Sti();
	return next_tty;
}


void
yield(void)
{
	_Cli();
	set_status(READY);
	scheduler();
}

void
waitpid(unsigned int pid)
{
	/* Setear el estado a bloqueado y elegir una nueva tarea */

	set_status(WAITCHILD);
	set_task_waitpid(pid);

	/* Cambiar de proceso en ejecucion*/
	scheduler();

	return;
}

int
wait(void)
{
	return _waitpid(0);
}

void
sleep(unsigned int seconds)
{
	/* Setear el estado a SLEEPING e inicializar el tiempo */
	_Cli();
	set_status(SLEEPING);
	set_task_ticks(seconds * 18);
	//_Sti();
	/* Cambiar de proceso en ejecucion */
	scheduler();
	return ;
}

/* Faults del procesador */
void bounds()
{
	puts("Bounds Exceeded!\n");
}

void invop()
{
	puts("Invalid OPCODE\n");
}

void div0()
{
	puts("Division by 0\n");
}


void gpf()
{
	puts("General Protection Fault\n");
}

void ssf()
{
	puts("Stack Segment Fault\n");
}


void snp()
{
	puts("Segment Not Present\n");
}

void pfault()
{
	puts("Page fault Exception\n");
	_doexit(1);
}
